﻿# define _CRT_SECURE_NO_WARNINGS 1
//深入理解指针2
//1. const修饰指针
//1.1 const修饰变量
//变量是可以修改的，如果把变量的地址交给⼀个指针变量，通过指针变量的也可以修改这个变量。

#include <stdio.h>
//int main()
//{
//	int s = 0;
//	s = 20;//s是可以被修改的
//	printf("%d", s);
//	const int a = 20;
//	a = 30;///a是不可以被修改的
//	return 0;
//}
//1.2 const修饰指针变量
//⼀般来讲const修饰指针变量，可以放在 * 的左边，也可以放在 * 的右边，意义是不⼀样的。
//int main()
//{
//	//int a = 10;
//	///*int* pa = &a;*/
//	///*const放在*的左边的情况*/
//	//const int* pa = &a;
//	///**pa = 30;*/
//	int b = 20;
//	//int* pa = &a;
//	//报错，const修饰指针变量，放在*号的左边，，限制的是*pa，pa指向的对象不可以更改，但是pa不受限制
//	//可以指针变量可以改变指向
//	/*const放在*的右边的情况*/
//	int c = 20;
//	int* const pc = &c;
//	*pc = 30;
//	printf("%d", *pc);
//	int* pc = &b;
//	//报错，const修饰指针变量放在*的右边，限制pc，也就是，pc的指向不能变，上面的情况中也就是pc
//	//只能指向从，但是*pc不受限制，也就是pc指向的内容可以更改，上面的情况中pc指向的内容由20变成了30
//	return 0;
//}
//结论：const修饰指针变量的时候
//• const如果放在* 的左边，修饰的是指针指向的内容，保证指针指向的内容不能通过指针来改变。
//但是指针变量本⾝的内容可变。
//• const如果放在* 的右边，修饰的是指针变量本⾝，保证了指针变量的内容不能修改，但是指针指
//向的内容，可以通过指针改变。
//2. 野指针
//概念：野指针就是指针指向的位置是不可知的（随机的、不正确的、没有明确限制的）
//2.1 野指针成因
//1. 指针未初始化
//int main()
//{
//	int* pa;
//	*pa = 20;//局部变量指针未初始化，默认为随机值 
//	printf("%d", *pa);
//	return 0;
//}
//2. 指针越界访问
//int main()
//{
//	int arr[10] = { 0 };
//	int* pa = &arr[0];
//	for (int i = 0; i <= 11; i++)
//	{
//		*(pa++) = i;//当指针指向的范围超出数组arr的范围时，p就是野指针 
//
//		printf("%d", *pa);
//	}
//	return 0;
//}
//3. 指针指向的空间释放
//int* test()
//{
//	int n = 10;
//	return &n;
//}
//int main()
//{
//	int* p = test();
//	printf("%d", *p);
//	return 0;
//}
//2.2 如何规避野指针
//2.2.1 指针初始化
//如果明确知道指针指向哪⾥就直接赋值地址，如果不知道指针应该指向哪⾥，可以给指针赋值NULL.
//NULL 是C语⾔中定义的⼀个标识符常量，值是0，0也是地址，这个地址是⽆法使⽤的，读写该地址
//会报错。
//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	int* pb = NULL;
//	return 0;
//}
//2.2.2 ⼩⼼指针越界
//2.2.3 指针变量不再使⽤时，及时置NULL，指针使⽤之前检查有效性
//当指针变量指向⼀块区域的时候，我们可以通过指针访问该区域，后期不再使⽤这个指针访问空间的
//时候，我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是：只要是NULL指针就不去访问，
//同时使⽤指针之前可以判断指针是否为NULL。
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = &arr[0];
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*(p++) = i;
//	}
//	//此时p已经越界了，可以把p置为NULL 
//	p = NULL;
//	//下次使⽤的时候，判断p不为NULL的时候再使⽤ 
//	//...
//	p = &arr[0];//重新让p获得地址 
//	if (p != NULL) //判断 
//	{
//		//...
//	}
//
//	return 0;
//}
//2.2.4 避免返回局部变量的地址
//3. assert断⾔
//assert.h 头⽂件定义了宏 assert() ，⽤于在运⾏时确保程序符合指定条件，如果不符合，就报
//错终⽌运⾏。这个宏常常被称为“断⾔”。
//assert() 宏接受⼀个表达式作为参数。如果该表达式为真（返回值⾮零）， assert() 不会产⽣
//任何作⽤，程序继续运⾏。如果该表达式为假（返回值为零）， assert() 就会报错，在标准错误
//流 stderr 中写⼊⼀条错误信息，显⽰没有通过的表达式，以及包含这个表达式的⽂件名和⾏号。
#include <assert.h>
//
//int main()
//{
//	for (int i = 0; i <= 10; i++)
//	{
//		int a = i;
//	}
//	assert(a != 10);
//	return 0;
//}
//4. 指针的使⽤和传址调⽤
//4.1 strlen的模拟实现
//库函数strlen的功能是求字符串⻓度，统计的是字符串中 \0 之前的字符的个数。
//函数原型如下：
//int my_strlen(const char* str)
//{
//	int count = 0;
//	assert(str);
//	while (*str)
//	{
//		count++;
//		str++;
//	}
//	return count;
//}
//int main()
//{
//	int a = my_strlen("abcdef");
//	printf("%d", a);
//	return 0;
//}
//4.2 传值调⽤和传址调⽤
//学习指针的⽬的是使⽤指针解决问题，那什么问题，⾮指针不可呢？
//例如：写⼀个函数，交换两个整型变量的值
//⼀番思考后，我们可能写出这样的代码：
//void swap(int x, int y)
//{
//	int temp = 0;
//	temp = x;
//	x = y;
//	y = temp;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	printf("交换前 a = % d    b = % d\n", a, b);
//	swap(a, b);
//	printf("交换后 a = % d    b = % d\n", a, b);
//	return 0;
//}
////为什么没有交换呢
//我们发现在main函数内部，创建了a和b，a的地址是0x00cffdd0，b的地址是0x00cffdc4，在调⽤
//Swap1函数时，将a和b传递给了Swap1函数，在Swap1函数内部创建了形参x和y接收a和b的值，但是
//x的地址是0x00cffcec，y的地址是0x00cffcf0，x和y确实接收到了a和b的值，不过x的地址和a的地址不
//⼀样，y的地址和b的地址不⼀样，相当于x和y是独⽴的空间，那么在Swap1函数内部交换x和y的值，
//⾃然不会影响a和b，当Swap1函数调⽤结束后回到main函数，a和b的没法交换。Swap1函数在使⽤
//的时候，是把变量本⾝直接传递给了函数，这种调⽤函数的⽅式我们之前在函数的时候就知道了，这
//种叫传值调⽤。
//传a，b的值无法成功，传a，b的地址呢
//void swap2(int* x, int* y)
//{
//	int temp = 0;
//	temp = *x;
//	*x = *y;
//	*y = temp;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	printf("交换前 a = % d    b = % d\n", a, b);
//	swap2(&a, &b);
//	printf("交换后 a = % d    b = % d\n", a, b);
//	return 0;
//}
//我们可以看到实现成Swap2的⽅式，顺利完成了任务，这⾥调⽤Swap2函数的时候是将变量的地址传
//递给了函数，这种函数调⽤⽅式叫：传址调⽤。	
// 第13讲：深⼊理解指针(3)
//1. 数组名的理解
//在上⼀个章节我们在使⽤指针访问数组的内容时，有这样的代码：
//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//int* p = &arr[0];
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	printf("&arr[0] = %p\n", &arr[0]);
//	printf("&arr[0] +1= %p\n", &arr[0]+1);
//	printf("arr     = %p\n", arr);
//	printf("arr  +1   = %p\n", arr+1);
//    printf("&arr    = %p\n", &arr);
//	printf("&arr +1   = %p\n", &arr+1);
//	return 0;
//}
//运行结果如下:
//&arr[0] = 0000001201F6F6C8
//arr = 0000001201F6F6C8
//可以得出数组名是首元素的地址，但也由两种特殊情况
/*int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d",sizeof(arr));
	return 0;
}
运行结果：40；
其实数组名就是数组⾸元素(第⼀个元素)的地址是对的，但是有两个例外：
• sizeof(数组名)，sizeof中单独放数组名，这⾥的数组名表⽰整个数组，计算的是整个数组的⼤⼩，
单位是字节
•& 数组名，这⾥的数组名表⽰整个数组，取出的是整个数组的地址（整个数组的地址和数组⾸元素
的地址是有区别的）
除此之外，任何地⽅使⽤数组名，数组名都表⽰⾸元素的地址*/
//2. 使⽤指针访问数组
//int main()
//{
//	int arr[10] = { 0 };
//	int* p = arr;
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	输入：
//	for (i = 0; i < sz; i++)
//	{
//		scanf("%d", p + i);
//	}
//	输出：
//	for (i = 0; i < sz; i++)
//	{
//		/*printf("%d ", p[i]);*/
//		printf("%d ", *(p+i));
//	}
//	return 0;
//}
//3. ⼀维数组传参的本质
//void test(int arr[10])
//{
//	int sz2 = sizeof arr / sizeof arr[0];
//	printf("sz2 == %d\n", sz2);
//}
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz1 = sizeof arr / sizeof arr[0];
//	printf("sz1 == %d\n", sz1);
//	test(arr);
//	return 0;
//}
//输出结果：
//sz1 == 10
//sz2 == 2
//数组名是数组⾸元素的地址；那么在数组传参的时候，
//传递的是数组名，也就是说本质上数组传参传递的是数组⾸元素的地址。
//所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写
//sizeof(arr) 计算的是⼀个地址的⼤⼩（单位字节）⽽不是数组的⼤⼩（单位字节）。正是因为函
//数的参数部分是本质是指针，所以在函数内部是没办法求的数组元素个数的。
//void test(int arr[10])
//{
//	int sz2 = sizeof arr / sizeof arr[0];
//	printf("sz2 == %d\n", sz2);
//}
//void test(int arr[])//参数写成数组形式，本质上还是指针
//{
//printf("%d\n", sizeof(arr));
//}
//
//void test2(int* arr)
//{
//	printf("%d\n", sizeof(arr));
//}
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz1 = sizeof arr / sizeof arr[0];
//	printf("sz1 == %d\n", sz1);
//	/*test1(arr);*/
//	test(arr);
//	test2(arr);
//	return 0;
//}
//总结：⼀维数组传参，形参的部分可以写成数组的形式，也可以写成指针的形式
//4. 冒泡排序
//冒泡排序的核⼼思想就是：两两相邻的元素进⾏⽐较。
//void print_arr(int arr[], int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//}
//void bubble_arr(int arr[], int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz - 1; i++)
//	{
//		int j = 0;
//		for (j = 0; j <sz - 1 -i ; j++)
//		{
//			if (arr[j] > arr[j + 1])
//			{
//				int temp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = temp;
//			}
//		}
//	}
//}
//int main()
//{
//	int arr[10] = { 1,3,6,2,8,4,7,5,9,0 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	打印交换前的数组
//	print_arr(arr,sz);
//	将数组进行排序
//	bubble_arr(arr, sz);
//	打印交换后的数组
//	print_arr(arr, sz);
//	return 0;
//}
//void print_arr(int arr[], int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//}
////优化：
//void bubble_arr(int arr[], int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz - 1; i++)
//	{
//		int j = 0;
//		int flag = 1;//假设这一趟已经有序了
//		for (j = 0; j < sz - 1 - i; j++)
//		{
//			if (arr[j] > arr[j + 1])
//			{
//				int temp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = temp;
//				flag = 0;//flag改为0，说明还是需要排序
//			}
//		}
//		if (flag = 1)
//			break;
//	}
//	
//}
//int main()
//{
//	int arr[10] = {1,2,3,4,5,6,7,8,9,10 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	//打印交换前的数组
//	print_arr(arr, sz);
//	//将数组进行排序
//	bubble_arr(arr, sz);
//	//打印交换后的数组
//	print_arr(arr, sz);
//	return 0;
//}
//5. ⼆级指针
//指针变量也是变量，是变量就有地址，那指针变量的地址存放在哪⾥？
//这就是 ⼆级指针 。
//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	int** ppa = &pa;
//	 **ppa = 20;
//	printf("%d\n", *pa);
//	printf("%d\n", *ppa);
//	printf("%d\n", **ppa);
//	return 0;
//}
//6. 指针数组
//指针数组是指针还是数组？
//我们类⽐⼀下，整型数组，是存放整型的数组，字符数组是存放字符的数组。那指针数组呢？是存放指针的数组。
//7. 指针数组模拟⼆维数组
//int main()
//{
//	int arr1[] = { 1,2,3,4,5 };
//	int arr2[] = { 2,3,4,5,6 };
//	int arr3[] = { 3,4,5,6,7 };
//	int* parr[3] = { arr1,arr2,arr3 };
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < 3; i++)
//	{
//		for (j = 0; j < 5; j++)
//		{
//			printf("%d ", parr[i][j]);
//		}
//		printf("\n");
//	}
//	return 0;
//}
//parr[i]是访问parr数组的元素，parr[i]找到的数组元素指向了整型⼀维数组，parr[i][j]就是整型⼀维数
//组中的元素。
//上述的代码模拟出⼆维数组的效果，实际上并⾮完全是⼆维数组，因为每⼀⾏并⾮是连续的。
